/*  This file is part of -_-.

    -_- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    -_- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with -_-.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2012 Oliver Katz */

#include "display_engine.h"

namespace sleepyface
{
	namespace visual
	{
		namespace display_engine
		{
			DisplayBox::DisplayBox()
			{
				width = 0;
				height = 0;
			}

			DisplayBox::DisplayBox(int w, int h)
			{
				width = w;
				height = h;

				if (width < 0)
					width = 0;

				if (height < 0)
					height = 0;
			}

			DisplayBox DisplayBox::error(int c)
			{
				if (c > 0)
				{
					width = -c;
					height = -c;
				}
				else
				{
					width = 0;
					height = 0;
				}
			}

			int DisplayBox::getWidth()
			{
				if (width < 0)
					return 0;
				else
					return width;
			}

			int DisplayBox::getHeight()
			{
				if (height < 0)
					return 0;
				else
					return height;
			}

			bool DisplayBox::empty()
			{
				return (width == 0 && height == 0);
			}

			bool DisplayBox::isError()
			{
				return (width < 0 && height < 0);
			}

			int DisplayBox::errorCode()
			{
				if (!isError())
					return 0;
				else if (width != height)
					return 0;
				else
					return -width;
			}

			int DisplayBox::compare(DisplayBox d)
			{
				if (width != d.width)
					return 1;
				if (height != d.height)
					return 2;
				return 0;
			}

			WidgetRenderer::WidgetRenderer()
			{
				sizer = NULL;
				renderer = NULL;
			}

			WidgetRenderer::WidgetRenderer(WidgetSizeCallback s, WidgetRenderCallback r)
			{
				sizer = s;
				renderer = r;
			}

			int WidgetRenderer::bindSizer(WidgetSizeCallback s)
			{
				sizer = s;
				return 0;
			}

			int WidgetRenderer::bindRenderer(WidgetRenderCallback r)
			{
				renderer = r;
				return 0;
			}

			DisplayBox WidgetRenderer::requestSize(layout_engine::Widget w, DisplayBox hint)
			{
				return *sizer(&w, &hint);
			}

			int WidgetRenderer::render(layout_engine::Widget w, int x, int y, DisplayBox size)
			{
				if (size.empty())
					return 0;
				DisplayBox db = *renderer(&w, x, y, &size);
				if (db.isError())
					return 1;
				if (db.compare(size) != 0)
					return 2;
				return 0;
			}

			ContainerRenderer::ContainerRenderer()
			{
				sizer = NULL;
				renderer = NULL;
			}

			ContainerRenderer::ContainerRenderer(ContainerSizeCallback s, ContainerHinterCallback h, ContainerRenderCallback r)
			{
				sizer = s;
				hinter = h;
				renderer = r;
			}

			int ContainerRenderer::bindSizer(ContainerSizeCallback s)
			{
				sizer = s;
				return 0;
			}

			int ContainerRenderer::bindHinter(ContainerHinterCallback h)
			{
				hinter = h;
				return 0;
			}

			int ContainerRenderer::bindRenderer(ContainerRenderCallback r)
			{
				renderer = r;
				return 0;
			}

			DisplayBox ContainerRenderer::requestSize(layout_engine::Container c, DisplayBox hint)
			{
				return *sizer(&c, &hint);
			}

			vector<pair<DisplayBox, DisplayBox> > ContainerRenderer::requestHints(layout_engine::Container c, DisplayBox size)
			{
				DisplayBox *boxes = hinter(&c, &size);
				vector<pair<DisplayBox, DisplayBox> > rtn;
				if (boxes == NULL)
					return rtn;
				pair<DisplayBox, DisplayBox> tmp;
				for (int i = 0; !boxes[i].empty(); i++)
				{
					if (i%2 == 0)
					{
						tmp.first = boxes[i];
					}
					else
					{
						tmp.second = boxes[i];
						rtn.push_back(tmp);
					}
				}
				return rtn;
			}

			int ContainerRenderer::render(layout_engine::Container c, int x, int y, DisplayBox size)
			{
				DisplayBox db = *renderer(&c, x, y, &size);
				if (db.isError())
					return 1;
				if (db.compare(size) != 0)
					return 2;
				return 0;
			}

			DisplayEngine::DisplayEngine()
			{
			}

			int DisplayEngine::addWidgetRenderer(string n, WidgetRenderer r)
			{
				widgetRenderers[n] = r;
				return 0;
			}

			int DisplayEngine::addContainerRenderer(string n, ContainerRenderer r)
			{
				containerRenderers[n] = r;
				return 0;
			}

			int DisplayEngine::recursivelyRenderContainer(layout_engine::Container c, int x, int y, DisplayBox h)
			{
				if (containerRenderers.find(c.getName()) != containerRenderers.end())
				{
					DisplayBox size = containerRenderers[c.getName()].requestSize(c, h);
					if (size.empty())
					{
						cout << "error: container has no render area\n";
						return 2;
					}
					else if (size.isError())
					{
						cout << "error: request for render area for container returned error\n";
						cout << "error: code: " << size.errorCode() << "\n";
						return 3;
					}
					pair<DisplayBox, DisplayBox> tmp;
					tmp.first = DisplayBox(x, y);
					tmp.second = size;
					coords[c.getName()] = tmp;
					vector<pair<DisplayBox, DisplayBox> > hints = containerRenderers[c.getName()].requestHints(c, size);
					if (hints.size() != c.size())
					{
						cout << "error: number of render area hints doesn't match container contents\n";
						return 4;
					}
					if (containerRenderers[c.getName()].render(c, x, y, size) != 0)
					{
						cout << "error: cannot render container: " << c.getName() << "\n";
						return 5;
					}

					for (int i = 0; i < c.size(); i++)
					{
						if (c.isContainer(i))
						{
							if (recursivelyRenderContainer(c.getContainer(i), x+hints[i].second.getWidth(), y+hints[i].second.getHeight(), hints[i].first) != 0)
							{
								cout << "error: error when rendering sub-container: " << c.getName() << ":" << c.getContainer(i).getName() << "[" << i << "]\n";
								return 6;
							}
						}
						else
						{
							if (widgetRenderers.find(c.getWidget(i).getType()) != widgetRenderers.end())
							{
								DisplayBox size = widgetRenderers[c.getWidget(i).getType()].requestSize(c.getWidget(i), hints[i].first);
								if (size.empty())
								{
									cout << "error: container has no render area\n";
									return 8;
								}
								else if (size.isError())
								{
									cout << "error: request for render area for container returned error\n";
									cout << "error: code: " << size.errorCode() << "\n";
									return 9;
								}
								pair<DisplayBox, DisplayBox> tmp;
								tmp.first = DisplayBox(x+hints[i].second.getWidth(), y+hints[i].second.getHeight());
								tmp.second = size;
								coords[c.getWidget(i).getName()] = tmp;

								if (widgetRenderers[c.getWidget(i).getType()].render(c.getWidget(i), x+hints[i].second.getWidth(), y+hints[i].second.getHeight(), hints[i].first) != 0)
								{
									cout << "error: error when rendering widget: " << c.getWidget(i).getName() << "\n";
									return 10;
								}
							}
							else
							{
								cout << "error: no renderer for widget: " << c.getWidget(i).getType() << "\n";
								return 7;
							}
						}
					}
				}
				else
				{
					cout << "error: no renderer for container: " << c.getName() << "\n";
					return 1;
				}

				return 0;
			}

			string DisplayEngine::getNameFromCoordinatesInContainer(layout_engine::Container c, int x, int y)
			{
				DisplayBox off = coords[c.getName()].first;
				DisplayBox size = coords[c.getName()].second;
				if ((x >= off.getWidth() && y >= off.getHeight()) && (x <= off.getWidth()+size.getWidth() && y <= off.getHeight()+size.getHeight()))
				{
					string override = "";
					for (int i = 0; i < c.size(); i++)
					{
						if (c.isContainer(i))
						{
							string tmp = getNameFromCoordinatesInContainer(c.getContainer(i), x, y);
							if (!tmp.empty())
								override = tmp;
						}
						else if (c.isWidget(i))
						{
							DisplayBox off = coords[c.getWidget(i).getName()].first;
							DisplayBox size = coords[c.getWidget(i).getName()].second;
							if ((x >= off.getWidth() && y >= off.getHeight()) && (x <= off.getWidth()+size.getWidth() && y <= off.getHeight()+size.getHeight()))
							{
								override = c.getWidget(i).getName();
							}
						}
					}

					if (override.empty())
						return c.getName();
					else
						return override;
				}
				else
				{
					return "";
				}
			}
		}
	}
}
